using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using System.Threading;
using CI.Interfaces;

namespace CI.Organism
{
    /// <summary>List of Organism's in a environment</summary>    
    [DataContract(IsReference = true)]
    public class OrganismCollection : IOrganismCollection
    {

        #region properties
        /// <summary>Alive Organisms Collection</summary>
        [DataMember]
        public SynchronizedCollection<IOrganism> Alive
        {
            get
            {
                return _alive;
            }
            set
            {
                _alive = value;
            }
        }
        private SynchronizedCollection<IOrganism> _alive = new SynchronizedCollection<IOrganism>();

        /// <summary>Dead Organisms Collection</summary>
        [DataMember]
        public SynchronizedCollection<IOrganism> Dead
        {
            get
            {
                return _dead;
            }
            set
            {
                _dead = value;
            }
        }
        private SynchronizedCollection<IOrganism> _dead = new SynchronizedCollection<IOrganism>();

        /// <summary>Inactive organisms, they are dead and not not on the screen</summary>
        [DataMember]
        public SynchronizedCollection<IOrganism> Inactive
        {
            get { return _inactive; }
            set { _inactive = value; }
        }
        private SynchronizedCollection<IOrganism> _inactive = new SynchronizedCollection<IOrganism>();

        /// <summary>Hosting Environment the Organism belong to</summary>
        [DataMember]
        public IHostingEnvironment hostEnvironment
        {
            get
            {
                return _hostEnvironment;
            }
            set
            {
                if (value == null)
                    throw new ArgumentException("hostingEnvironment");

                _hostEnvironment = value;
                // save the Organism in the hosting environment as well
                _hostEnvironment.Organisms = this;
            }
        }
        private IHostingEnvironment _hostEnvironment;

        /// <summary>Sub colleciton of all the organisms which have the dirty location flagged mark and need updating</summary>
        public IList<IOrganism> DirtyLocations
        {
            get { return _dirtyLocations; }
            set { _dirtyLocations = value; }
        }
        private IList<IOrganism> _dirtyLocations = new List<IOrganism>();

        private long _lastDirtyRebuildHB;

        #endregion

        #region Alive, Dead, Inactive add and remove with event notifiers

        public event Action<IOrganism> OnAliveAddEvent;
        public event Action<IOrganism> OnAliveRemoveEvent;
        public event Action<IOrganism> OnDeadAddEvent;
        public event Action<IOrganism> OnDeadRemoveEvent;
        public event Action<IOrganism> OnInactiveAddEvent;
        public event Action<IOrganism> OnInactiveRemoveEvent;

        public void AliveAdd(IOrganism organism)
        {
            Alive.Add(organism);
            if (OnAliveAddEvent != null)
                OnAliveAddEvent(organism);
        }
        public void AliveRemove(IOrganism organism)
        {
            Alive.Remove(organism);
            if (OnAliveRemoveEvent != null)
                OnAliveRemoveEvent(organism);
        }
        public void DeadAdd(IOrganism organism)
        {
            Dead.Add(organism);
            if (OnDeadAddEvent != null)
                OnDeadAddEvent(organism);
        }
        public void DeadRemove(IOrganism organism)
        {
            Dead.Remove(organism);
            if (OnDeadRemoveEvent != null)
                OnDeadRemoveEvent(organism);
        }
        public void InactiveAdd(IOrganism organism)
        {
            Inactive.Add(organism);
            if (OnInactiveAddEvent != null)
                OnInactiveAddEvent(organism);
        }
        public void InactiveRemove(IOrganism organism)
        {
            Inactive.Remove(organism);
            if (OnInactiveRemoveEvent != null)
                OnInactiveRemoveEvent(organism);
        }
        #endregion

        /// <summary>
        /// creates a new list based on the organisms if they have the dirty flag set and clears the organisms dirty flag
        /// Note : if its already been done this heartbeat it wont happen again.
        /// </summary>
        public void DirtyLocationsRebuild()
        {
            if (Monitor.TryEnter(DirtyLocations, Globals.CriticalAcquireLockTimeout))
            {

                if (_lastDirtyRebuildHB == SimEnv.Instance.HostEnv.SystemHeartBeat)
                    return;

                _lastDirtyRebuildHB = SimEnv.Instance.HostEnv.SystemHeartBeat;

                try
                {
                    // clear the exisitng list for starters
                    DirtyLocations.Clear();

                    // cycle through all alive organisms and add any dirty ones to the dirty list
                    if (Monitor.TryEnter(Alive, Globals.CriticalAcquireLockTimeout))
                    {
                        try
                        {
                            foreach (IOrganism organism in Alive)
                            {
                                IfDirtyAddToDirtyListThenClear(organism);
                            }
                        }
                        finally
                        {
                            Monitor.Exit(Alive);
                        }
                    }
                    else
                    {
                        throw new OrganismException("Unable to acquire lock on Alive");
                    }

                    // cycle through all dead organisms and add any dirty ones to the dirty list
                    if (Monitor.TryEnter(Dead, Globals.CriticalAcquireLockTimeout))
                    {
                        try
                        {
                            foreach (IOrganism organism in Dead)
                            {
                                IfDirtyAddToDirtyListThenClear(organism);
                            }
                        }
                        finally
                        {
                            Monitor.Exit(Dead);
                        }
                    }
                    else
                    {
                        throw new OrganismException("unable to acquire lock on Dead");
                    }
                }
                finally
                {
                    Monitor.Exit(DirtyLocations);
                }
            }
            else
            {
                throw new OrganismException("Unable to acquire exception on DirtyLocations");
            }
        }

        /// <summary>
        /// If an organism is dirty add it to the dirty list the clear
        /// </summary>
        /// <param name="organism"></param>
        private void IfDirtyAddToDirtyListThenClear(IOrganism organism)
        {
            // gets its latest location if it has not already been marked as dirty
            // this will trigger a check which will update the dirty location flag if so
            ILocation _location;
            if (!organism.LocationDirty)
                _location = organism.Location;

            // check to make sure they are registered in the environment
            if (!(organism.HostingEnvironment.HostingEnvironmentPhysical.Registered(organism)))
                return;

            // if its dirty
            if (organism.LocationDirty)
            {
                // else add it to the list
                DirtyLocations.Add(organism);

                // clear it
                organism.LocationDirty = false;
            }
        }

        /// <summary>Find the Organism with the specifed guid, checks Alive, Dead and Inactive lists</summary>
        /// <param name="guid">guid to find</param>
        /// <returns>Organism</returns>
        public IOrganism Find(Guid guid)
        {
            // check in the alive
            if (Monitor.TryEnter(Alive, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    foreach (IOrganism organism in Alive)
                    {
                        if (organism.Guid == guid)
                            return organism;
                    }
                }
                finally
                {
                    Monitor.Exit(Alive);
                }
            }
            else
            {
                throw new OrganismException("unable to acquire lock on Alive");
            }

            // check in the Dead
            if (Monitor.TryEnter(Dead, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    foreach (IOrganism organism in Dead)
                    {
                        if (organism.Guid == guid)
                            return organism;
                    }
                }
                finally
                {
                    Monitor.Exit(Dead);
                }
            }
            else
            {
                throw new OrganismException("Unable to acquire lock on Dead");
            }


            // check in the inactive
            if (Monitor.TryEnter(Inactive, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    foreach (IOrganism organism in Inactive)
                    {
                        if (organism.Guid == guid)
                            return organism;
                    }
                }
                finally
                {
                    Monitor.Exit(Inactive);
                }
            }
            else
            {
                throw new OrganismException("Unable to acquire lock on Inactive");
            }

            // if not found return null
            return null;
        }

        public bool Contains(IOrganism searchOrganism)
        {
            // check in the alive
            if (Monitor.TryEnter(Alive, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    foreach (IOrganism organism in Alive)
                    {
                        if (organism == searchOrganism)
                            return true;
                    }
                }
                finally
                {
                    Monitor.Exit(Alive);
                }
            }
            else
            {
                throw new OrganismException("unable to acquire lock on Alive");
            }

            // check in the Dead
            if (Monitor.TryEnter(Dead, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    foreach (IOrganism organism in Dead)
                    {
                        if (organism == searchOrganism)
                            return true;
                    }
                }
                finally
                {
                    Monitor.Exit(Dead);
                }
            }
            else
            {
                throw new OrganismException("Unable to acquire lock on Dead");
            }


            // check in the inactive
            if (Monitor.TryEnter(Inactive, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    foreach (IOrganism organism in Inactive)
                    {
                        if (organism == searchOrganism)
                            return true;
                    }
                }
                finally
                {
                    Monitor.Exit(Inactive);
                }
            }
            else
            {
                throw new OrganismException("Unable to acquire lock on Inactive");
            }

            // if not found return false
            return false;
            
        }

        /// <summary>
        /// return the enumerator we are going to use
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>Allows us to iterate over all the organisms, dead or alive only, note: inactive is not included</summary>
        public IEnumerator<IOrganism> GetEnumerator()
        {
            // locking (this) is not good see article http://www.developerfusion.com/article/9397/iterator-blocks-in-c-2-autogenerated-state-machines/
            // todo : refactor this code to be better without locking
            lock (this)
            {
                // go through all the alive organisms
                if (Monitor.TryEnter(Alive, Globals.CriticalAcquireLockTimeout))
                {
                    try
                    {
                        foreach (IOrganism organism in Alive)
                        {
                            yield return organism;
                        }
                    }
                    finally
                    {
                        Monitor.Exit(Alive);
                    }
                }
                else
                {
                    throw new OrganismException("Unable to acquire lock on Alive");
                }

                // go through all the dead organisms if they are still on the screen and need to be processed
                if (Monitor.TryEnter(Dead, Globals.CriticalAcquireLockTimeout))
                {
                    try
                    {
                        foreach (IOrganism organism in Dead)
                        {
                            if (organism.ActiveState == ActiveState.Process)
                                yield return organism;
                        }
                    }
                    finally
                    {
                        Monitor.Exit(Dead);
                    }
                }
                else
                {
                    throw new OrganismException("Unable to acquire lock on Dead");
                }

                yield break;
            }
        }

        /// <summary>
        /// ToString with a fitness function
        /// </summary>
        /// <returns></returns>
        public string ToString(IFitnessFunction fitnessFunction)
        {
            var sb = new StringBuilder();
            sb.AppendLine("Loop exiting, calculating fittest organism and dumping them to output");

            if (fitnessFunction == null)
                return sb + "No Fitness function defined";

            List<IOrganism> fittest = fitnessFunction.Fittest(this);

            foreach (IOrganism organism in fittest)
            {
                sb.AppendLine(string.Format("Organism {0} with guid {1}", organism, organism.Guid));
                sb.AppendLine(string.Format("Strengh : {0}", fitnessFunction.EvaluateFitnessAndUpdateInternalFitnessValue(organism)));
                if (organism.NeuralSystem != null)
                {
                    sb.AppendLine(organism.NeuralSystem.ToString());
                }
            }

            // return result
            return sb.ToString();
        }

    }
}